/** @file res_shader_prog.cpp
    @brief Program of shaders */

#include "res/res_shader_prog.h"

//==============================================================================
// Constructors
//==============================================================================
G3::SHADER_PROGRAM::SHADER_PROGRAM () {
    Log = &G3::MainLog;
    programObject = 0;
}

//==============================================================================
// Destructors
//==============================================================================
G3::SHADER_PROGRAM::~SHADER_PROGRAM () {
	Clear ();
}

//==============================================================================
// = operators
//==============================================================================
G3::SHADER_PROGRAM& G3::SHADER_PROGRAM::operator=(const SHADER_PROGRAM &program) {
    Log = program.Log;
    programObject = program.programObject;
    vertexShaders = program.vertexShaders;
    pixelShaders = program.pixelShaders;

    return *this;
}

//==============================================================================
// Attaches a new shader to the program
//==============================================================================
bool G3::SHADER_PROGRAM::AttachShader (const std::string &Filename, SHADER_PROFILE Profile) {
    if (Profile == SP_VERTEX) {
        H_VSHADER handle = MainShaderManager.GetShader (Filename, Profile);

        if (!handle.IsNull ())
            vertexShaders.push_back (handle);
        else
            return false;

        return true;
    } else if (Profile == SP_FRAGMENT) {
        H_PSHADER handle = MainShaderManager.GetShader (Filename, Profile);

        if (!handle.IsNull ())
            pixelShaders.push_back (handle);
        else
            return false;

        return true;
    }

    return false;
}

//==============================================================================
// Detaches a shader from the program
//==============================================================================
bool G3::SHADER_PROGRAM::DetachShader (uint handle) {
    int i;

    for (i=0; i<(int)vertexShaders.size (); i++) {
        if (vertexShaders [i] == handle) {
            vertexShaders.erase (vertexShaders.begin () + i);
            return true;
        }
    }

    for (i=0; i<(int)pixelShaders.size (); i++) {
        if (pixelShaders [i] == handle) {
            pixelShaders.erase (pixelShaders.begin () + i);
            return true;
        }
    }

    return false;
}

bool G3::SHADER_PROGRAM::DetachShader (const std::string &Filename) {
    int i;

    for (i=0; i<(int)vertexShaders.size (); i++) {
        if (MainShaderManager.GetShaderName (vertexShaders [i], SP_VERTEX) == Filename) {
            vertexShaders.erase (vertexShaders.begin () + i);
            return true;
        }
    }

    for (i=0; i<(int)pixelShaders.size (); i++) {
        if (MainShaderManager.GetShaderName (pixelShaders [i], SP_FRAGMENT) == Filename) {
            pixelShaders.erase (pixelShaders.begin () + i);
            return true;
        }
    }

    return false;
}

//==============================================================================
// Gets a pointer to the shader by index
//==============================================================================
boost::shared_ptr<G3::SHADER_BASE> G3::SHADER_PROGRAM::GetShader (SHADER_PROFILE Profile, int index) {
    switch (Profile) {
        case SP_VERTEX:
            if (index < (int) vertexShaders.size ())
                return MainShaderManager.GetVSPointer (vertexShaders [index]);
            break;

        case SP_FRAGMENT:
            if (index < (int) pixelShaders.size ())
                return MainShaderManager.GetPSPointer (pixelShaders [index]);
            break;

        default: break;
    }
    return boost::shared_ptr<G3::SHADER_BASE>();
}

//==============================================================================
// Gets a pointer to the shader by filename
//==============================================================================
boost::shared_ptr<G3::SHADER_BASE> G3::SHADER_PROGRAM::GetShader (const std::string &filename) {
    if (filename.empty ())
        return boost::shared_ptr<G3::SHADER_BASE>();

    std::string path = MainFilesys.GetPath (filename.c_str ());
    std::string sname;
    int i;

    if (path.empty ())
        return boost::shared_ptr<G3::SHADER_BASE>();

    for (i=0; i<(int)vertexShaders.size (); i++) {
        sname = MainShaderManager.GetShaderName (vertexShaders [i], SP_VERTEX);

        if (sname == path)
            return MainShaderManager.GetVSPointer (vertexShaders [i]);
    }

    for (i=0; i<(int)pixelShaders.size (); i++) {
        sname = MainShaderManager.GetShaderName (pixelShaders [i], SP_FRAGMENT);

        if (sname == path)
            return MainShaderManager.GetPSPointer (pixelShaders [i]);
    }

    return boost::shared_ptr<G3::SHADER_BASE>();
}

//==============================================================================
// Applies the program
//==============================================================================
void G3::SHADER_PROGRAM::Apply () {
    int i;

    for (i=0; i<(int)vertexShaders.size (); i++)
        MainShaderManager.ApplyVS (vertexShaders [i]);

    for (i=0; i<(int)pixelShaders.size (); i++)
        MainShaderManager.ApplyPS (pixelShaders [i]);

    if (!vertexShaders.empty () || !pixelShaders.empty ())
        glUseProgramObjectARB (programObject);
}

//==============================================================================
// Unapplies the program
//==============================================================================
void G3::SHADER_PROGRAM::UnApply () {
    int i;

    for (i=0; i<(int)vertexShaders.size (); i++)
        MainShaderManager.UnApplyVS (vertexShaders [i]);

    for (i=0; i<(int)pixelShaders.size (); i++)
        MainShaderManager.UnApplyPS (pixelShaders [i]);

    if (!vertexShaders.empty () || !pixelShaders.empty ())
        glUseProgramObjectARB (programObject);

    if (!vertexShaders.empty () || !pixelShaders.empty ())
        glUseProgramObjectARB (0);
}

//==============================================================================
// Links the program's shaders
//==============================================================================
bool G3::SHADER_PROGRAM::Link () {
    int error = 0;
    char str [4096];

    if (vertexShaders.empty () && pixelShaders.empty ()) {
        if (Log)
            Log->Report ("ERROR: SHADER_PROGRAM::Link: Nothing to link\n");
        return false;
    }

    // We can only link GLSL objects
    for (int i=0; i<(int)vertexShaders.size (); i++) {
        if (MainShaderManager.GetVSFormat (vertexShaders [i]) != SF_GLSL) {
            if (Log)
                Log->Report ("ERROR: SHADER_PROGRAM::Link: Nothing to link\n");
            return false;
        }
    }

    for (int i=0; i<(int)pixelShaders.size (); i++) {
        if (MainShaderManager.GetPSFormat (pixelShaders [i]) != SF_GLSL) {
            if (Log)
                Log->Report ("ERROR: SHADER_PROGRAM::Link: Nothing to link\n");
            return false;
        }
    }

    programObject = glCreateProgramObjectARB ();
	printf ("ProgOBJ: %d\n", programObject);

    // Attach vertex - and pixel shaders
    for (int i=0; i<(int)vertexShaders.size (); i++)
        glAttachObjectARB (programObject, MainShaderManager.GetVSID (vertexShaders [i]));

    for (int i=0; i<(int)pixelShaders.size (); i++)
        glAttachObjectARB (programObject, MainShaderManager.GetPSID (pixelShaders [i]));

    glLinkProgramARB (programObject);
    glGetObjectParameterivARB (programObject, GL_OBJECT_LINK_STATUS_ARB, &error);

    if (!error) {
        glGetInfoLogARB (programObject, sizeof (str), NULL, str);

        if (Log)
            Log->Report ("SHADER_PROGRAM::Link: %s\nFiles:\n\t", str);

        for (int i=0; i<(int)vertexShaders.size (); i++) {
            if (Log)
                Log->Report ("%s ", MainShaderManager.GetShaderName (vertexShaders [i], SP_VERTEX).c_str ());
        }
        for (int i=0; i<(int)pixelShaders.size (); i++) {
            if (Log)
                Log->Report ("%s ", MainShaderManager.GetShaderName (pixelShaders [i], SP_FRAGMENT).c_str ());
        }

        if (Log)
            Log->Report ("\n\n");
        return false;
    }
    return true;
}

//==============================================================================
// Clears the list of pointers, leaving all the shaders dangling
//==============================================================================
void G3::SHADER_PROGRAM::Clear () {
    for (int i=0; i<(int)vertexShaders.size (); i++) {
        if (MainShaderManager.GetVSFormat (vertexShaders [i]) != SF_GLSL) {
            vertexShaders.clear ();
            break;
        }
    }

    for (int i=0; i<(int)pixelShaders.size (); i++) {
        if (MainShaderManager.GetPSFormat (pixelShaders [i]) != SF_GLSL) {
            pixelShaders.clear ();
            break;
        }
    }

    if (!vertexShaders.empty ())
        vertexShaders.clear ();
    if (!pixelShaders.empty ())
        pixelShaders.clear ();

	if (programObject > 0)
	    glDeleteObjectARB (programObject);
    programObject = 0;
}
